---
title: Using the Terraform OpenStack Provider
---

<div class="container" style="padding-top: 0px;">
  <div class="row">
    <div class="col-12">
      <div class="jumbotron">
        <h1 class="display-3">
          Terraform OpenStack Provider
        </h1>
        <p class="lead">
          This is an example of how to utilize Kitchen-Terraform to test OpenStack resources configured with the <a href="https://www.terraform.io/docs/providers/openstack/index.html" style="color: #32c850;">Terraform OpenStack Provider</a>.
        </p>
        <div class="float-right">Author: Ewa Czechowska</div>
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col-4">
      <div class="list-group" id="list-tab" role="tablist">
        <a class="list-group-item list-group-item-action active" id="list-one-list" data-toggle="list" href="#list-one" role="tab" aria-controls="one">
          1. Requirements & Setup
        </a>
        <a class="list-group-item list-group-item-action" id="list-two-list" data-toggle="list" href="#list-two" role="tab" aria-controls="two">
          2. Create Terraform code
        </a>
        <a class="list-group-item list-group-item-action" id="list-three-list" data-toggle="list" href="#list-three" role="tab" aria-controls="three">
          3. Create Terraform variables
        </a>
        <a class="list-group-item list-group-item-action" id="list-four-list" data-toggle="list" href="#list-four" role="tab" aria-controls="four">
          4. Create Terraform outputs
        </a>
        <a class="list-group-item list-group-item-action" id="list-five-list" data-toggle="list" href="#list-five" role="tab" aria-controls="five">
          5. Setup provider information
        </a>
        <a class="list-group-item list-group-item-action" id="list-six-list" data-toggle="list" href="#list-six" role="tab" aria-controls="six">
          6. Create tests
        </a>
        <a class="list-group-item list-group-item-action" id="list-seven-list" data-toggle="list" href="#list-seven" role="tab" aria-controls="seven">
          7. Run tests
        </a>
      </div>
    </div>
    <div class="col-8">
      <div class="tab-content" id="nav-tabContent">
        <div class="tab-pane fade show active" id="list-one" role="tabpanel" aria-labelledby="list-one-list">
          We need two OpenStack networks that are accessible from localhost.
          <br><hr>
          To setup the repository, run these commands:
          <br><br>
          <% code("bash") do %>
mkdir -p openstack_provider_example/test/integration/example/controls
mkdir -p openstack_provider_example/dummy_keypair

ssh-keygen -f openstack_provider_example/dummy_keypair/cloud.key

cd openstack_provider_example
          <% end %>
          Create the <p class="font-weight-bold" style="color: #32c850; display: inline;">Gemfile</p> to install our dependencies.
          <br><br>
          <% code("ruby") do %>
source "https://rubygems.org/" do
  gem "kitchen-terraform"
end
          <% end %>
          Install Kitchen-Terraform and other rubygems, install bundler if not installed yet.
          <br><br>
          <% code("bash") do %>
gem install bundler
bundle install
          <% end %>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">.kitchen.yml</p> which brings together the Terraform module code and Inspec controls.
          <br><br>
          <div class="row">
            <div class="col">
              <% code("yml") do %>
---
driver:
  name: terraform
  command_timeout: 1000
  variable_files:
    - ./my-variables.tfvars

provisioner:
  name: terraform

transport:
  name: ssh
  ssh_key: ./dummy_keypair/cloud.key
  username: ubuntu

verifier:
  name: terraform
  groups:
    - name: master
      controls:
        - nano_installed
      hostnames: master_address
    - name: workers
      controls:
        - curl_installed
      hostnames: workers_addresses

platforms:
  - name: ubuntu

suites:
  - name: example


              <% end %>
            </div>
            <div class="col">
              <br><br>
              The Kitchen-Terraform driver is configured with a command timeout of 1000 seconds and the path to a Terraform variables file.
              <br><br><br>
              The Kitchen-Terraform provisioner is enabled.
              <br><br><br>
              The Test Kitchen SSH transport is configured to use the dummy_keypair and a static username for SSH authentication with the VMs.
              <br><br>
              The Kitchen-Terraform verifier is configured with two groups.
              <br><br>
              The master group is configured to run a control against the master VM by using the master_address output for the value of hostnames.
              <br><br>
              The workers group is configured to run a control against all of the worker VMs by using the workers_addresses output for the value of hostnames.
              <br><br>
              The platforms provide arbitrary grouping for the test suite matrix.
              <br><br>
              The suite name corresponds to the directory containing the Inspec profile: <% code("bash") do %>test/integration/example/<% end %>
            </div>
          </div>
        </div>
        <div class="tab-pane fade" id="list-two" role="tabpanel" aria-labelledby="list-two-list">
          Example Terraform code using the OpenStack provider is below. The resources created by this code is what we'll be testing later on.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">main.tf</p> and add each block of code into it.
          <br><br>
          The configuration is restricted to Terraform versions equal to or greater than 0.10.2 and less than 0.11.0.
          <% code("ruby") do %>
terraform {
  required_version = "~> 0.10.2"
}
          <% end %>
          Enable OpenStack provider to pass in OpenStack information and user authentication.
          <br><br>
          <% code("ruby") do %>
provider "openstack" {
  auth_url    = "${var.provider_auth_url}"
  password    = "${var.provider_password}"
  region      = "${var.provider_region}"
  tenant_name = "${var.provider_tenant_name}"
  user_name   = "${var.provider_user_name}"
}
          <% end %>
          Creates a SSH keypair, IP address and Ubuntu server which will act as a master server.
          <br><br>
          <% code("ruby") do %>
resource "openstack_compute_keypair_v2" "kitchen-terraform" {
  name       = "kitchen-terraform-example"
  public_key = "${file("./dummy_keypair/cloud.key.pub")}"
}

resource "openstack_networking_floatingip_v2" "master" {
  pool = "${var.networking_floatingips_pool}"
}

resource "openstack_compute_instance_v2" "master" {
  flavor_name = "v.c1.m1024.d5.e0"
  floating_ip = "${element(openstack_networking_floatingip_v2.master.*.address, 0)}"
  image_name  = "ubuntu-16.04"
  key_pair    = "${openstack_compute_keypair_v2.kitchen-terraform.name}"
  name        = "kitchen-terraform-example-master"

  connection {
    host        = "${self.floating_ip}"
    private_key = "${file("./dummy/cloud.key")}"
    type        = "ssh"
    user        = "ubuntu"
  }

  metadata = {
    ssh_user = "ubuntu"
  }

  network {
    name = "${var.compute_instances_network_name}"
  }

  provisioner "remote-exec" {
    inline = ["sudo apt-get install --no-install-recommends --yes nano"]
  }
}
          <% end %>
          Uses the SSH key from above, creates a new IP address and Ubuntu server which will act as a worker server.
          <br><br>
          <% code("ruby") do %>
resource "openstack_networking_floatingip_v2" "workers" {
  count = 2
  pool  = "${var.networking_floatingips_pool}"
}

resource "openstack_compute_instance_v2" "worker" {
  count       = 2
  flavor_name = "v.c1.m1024.d5.e0"
  floating_ip = "${element(openstack_networking_floatingip_v2.workers.*.address, count.index)}"
  image_name  = "ubuntu-16.04"
  key_pair    = "${openstack_compute_keypair_v2.kitchen-terraform.name}"
  name        = "kitchen-terraform-example-worker-${count.index+1}"

  connection {
    host        = "${self.floating_ip}"
    private_key = "${file("./dummy/cloud.key")}"
    type        = "ssh"
    user        = "ubuntu"
  }

  metadata = {
    ssh_user = "ubuntu"
  }

  network {
    name = "${var.compute_instances_network_name}"
  }

  provisioner "remote-exec" {
    inline = ["sudo apt-get install --no-install-recommends --yes curl"]
  }
}
          <% end %>
        </div>
        <div class="tab-pane fade" id="list-three" role="tabpanel" aria-labelledby="list-three-list">
          We also need to setup variables as some networking and provider attributes are required to make the Terraform code work successfully.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">variable.tf</p> and add the below block of code into it.
          <br><br>
          <% code("ruby") do %>
variable "compute_instances_network_name" {
  description = "The human-readable name of the network of the compute instances"
  type        = "string"
}

variable "networking_floatingips_pool" {
  description = "The name of the pool from which to obtain the floating IP addresses"
  type        = "string"
}

variable "provider_auth_url" {
  description = "The identity authentication URL"
  type        = "string"
}

variable "provider_password" {
  description = "The password to login with"
  type        = "string"
}

variable "provider_region" {
  description = "The cloud region to use"
  type        = "string"
}

variable "provider_tenant_name" {
  description = "The name of the tenant to login with"
  type        = "string"
}

variable "provider_user_name" {
  description = "The user ID to login with"
  type        = "string"
}
          <% end %>
        </div>
        <div class="tab-pane fade" id="list-four" role="tabpanel" aria-labelledby="list-four-list">
          To assist in testing, Terraform outputs will provide the master and worker server addresses. The Kitchen-Terraform verifier can use these artifacts to validate the Terraform code.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">output.tf</p> and add each block of code into it.
          <br><br>
          <% code("ruby") do %>
output "master_address" {
  value = "${openstack_networking_floatingip_v2.master.address}"
}

output "workers_addresses" {
  value = ["${openstack_networking_floatingip_v2.workers.*.address}"]
}
          <% end %>
          Refer back to the .kitchen.yml and in the verifier section you will see a reference to the above hostnames output.
        </div>
        <div class="tab-pane fade" id="list-five" role="tabpanel" aria-labelledby="list-five-list">
          Before creating the tests, let's setup our OpenStack provider information. This helps get the URL, user authentication and a couple other provider specific items setup.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">my-variables.tfvars</p> and add the block of code into it.
          <br><br>
          <% code("ruby") do %>
compute_instances_network_name = "<VALUE>"
networking_floatingips_pool    = "<VALUE>"
provider_auth_url              = "<VALUE>"
provider_password              = "<VALUE>"
provider_region                = "<VALUE>"
provider_tenant_name           = "<VALUE>"
provider_user_name             = "<VALUE>"
          <% end %>
          Refer back to the .kitchen.yml and in the driver section you will see a reference to the above my-variables.tfvars file.
        </div>
        <div class="tab-pane fade" id="list-six" role="tabpanel" aria-labelledby="list-six-list">
          We've created the Terraform code, now it's time to create the Inspec control tests. Please see the <a href="https://www.inspec.io/docs/reference/profiles/" style="color: #32c850;">Inspec documentation</a> to learn more about profiles and controls.
          <br><br>
          Create a default profile file <p class="font-weight-bold" style="color: #32c850; display: inline;">test/integration/example/inspec.yml</p>
          <br><br>
          <% code("yml") do %>
---
name: default
          <% end %>
          Referring back to the .kitchen.yml file and inside the verifier section there is a nano_installed control which we need to create.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">test/integration/example/controls/nano_installed_spec.rb</p>
          <% code("ruby") do %>
# frozen_string_literal: true

control "nano_installed" do
  describe package "nano" do
    it do
      is_expected.to be_installed
    end
  end
end
          <% end %>
          Let's create the curl_installed control, which will validate curl is installed.
          <br><br>
          Create this file <p class="font-weight-bold" style="color: #32c850; display: inline;">test/integration/example/controls/curl_installed_spec.rb</p>
          <% code("ruby") do %>
# frozen_string_literal: true

control "curl_installed" do
  describe package "curl" do
    it do
      is_expected.to be_installed
    end
  end
end
          <% end %>
        </div>
        <div class="tab-pane fade" id="list-seven" role="tabpanel" aria-labelledby="list-seven-list">
          Execute Kitchen-Terraform by running these commands
          <br><br>
          <% code("ruby") do %>
# Create resources from the Terraform code in main.tf
bundle exec kitchen converge

# Run the Inspec controls from the .kitchen.yml verifier section
bundle exec kitchen verify
          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>
